/*
    Nios-sim - one simple NIOSII simulator only for personal interest and fun.
    Copyright (C) 2010  chysun2000@gmail.com

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include "public.h"
#include "niosii.h"
#include "io_device.h"
#include "instruction.h"

static struct NIOS_CPU cpu;
static struct image_info * img_info = NULL;

void reset_cpu(void)
{
	img_info = get_image_info();
	memset(&cpu, 0x00, sizeof(struct NIOS_CPU));
	/* set the default to control register */
	cpu.ctrl_regs[status] = 0x00800000;	
	cpu.gp_regs[4] = 0x00;
	cpu.gp_regs[7] = 0x00;
	cpu.trace_index = 0;
}

static uint32_t get_opcode(uint32_t intr)
{
	struct i_type_code * code = (struct i_type_code*)&intr;
	return code->op;
}

static uint32_t get_opxcode(uint32_t intr)
{
	struct r_type_code * code = (struct r_type_code *)&intr;
	return code->opx;
}

struct NIOS_CPU * get_nios_cpu(void)
{
	return &cpu;
}

static uint32_t handle_r_type_code(uint32_t opxcode, uint32_t code)
{
	return (r_type_handlers[opxcode]).handler(&cpu, code);
}

static uint32_t handle_i_type_code(uint32_t opcode, uint32_t code)
{
	return i_type_handlers[opcode].handler(&cpu, code);
}

static uint32_t handle_j_type_code(uint32_t opcode, uint32_t code)
{
	return j_type_handlers[opcode].handler(&cpu, code);
}

uint32_t get_offset_pc(uint32_t pc, uint32_t base_addr)
{
	return (pc - base_addr) / 4;
}

uint32_t get_instruct(struct NIOS_CPU * cpu, uint32_t * mem_base, 
								 uint32_t base_addr)
{
	uint32_t code = 0;
	uint32_t offset = get_offset_pc(cpu->pc, base_addr);

	code = mem_base[offset];
	return code;
}

static int is_j_type(uint32_t opcode)
{
	if (opcode == CALL || opcode == JMPI){
		return SIM_TRUE;
	}
	else {
		return SIM_FALSE;
	}
}

uint32_t execute(uint32_t code)
{
	uint32_t opcode = 0;
	uint32_t opxcode = 0;
	uint32_t pc_mod_type = 0;

	opcode = get_opcode(code);
	
	if (is_j_type(opcode) == SIM_TRUE){
		pc_mod_type = handle_j_type_code(opcode, code);
	}
	else if (opcode != OP_R_TYPE){
		pc_mod_type = handle_i_type_code(opcode, code);
	}
	else{
		opxcode = get_opxcode(code);
		pc_mod_type = handle_r_type_code(opxcode, code);
	}
	return pc_mod_type;
}

int32_t get_addr_type(uint32_t addr)
{
	if ((addr >= img_info->base_addr) 
		&& ((addr - img_info->base_addr) <= img_info->mem_size)){
		return MEM_ADDR;
	}
	else{
		return IO_ADDR;
	}
}

uint8_t get_byte(uint32_t addr)
{
	if (get_addr_type(addr) == MEM_ADDR){
		uint8_t * buf = (uint8_t *)img_info->mem_base;
		return buf[addr - img_info->base_addr];
	}
	else{
		struct io_device * dev = get_device(addr);
		if (dev != NULL){
			return dev->read_data(dev, addr, 1);
		}
		else {
			printf("%s->error at %x\n",__func__,addr);
			return 0;
		}
	}
}

void store_byte(uint32_t addr, unsigned char data)
{
	if(get_addr_type(addr) == MEM_ADDR){
		uint8_t * buf = (uint8_t *)img_info->mem_base;
		buf[addr - img_info->base_addr] = data;
	}
	else {
		struct io_device * dev = get_device(addr);
		if (dev != NULL){
			dev->write_data(dev, addr, data, 1);
		}
		else {
			printf("%s->unhandled data:%x at %x\n",__func__,data,addr);
		}
	}
}

uint16_t get_half_word(uint32_t addr)
{
	if (get_addr_type(addr) == MEM_ADDR){
		uint16_t * buf = (uint16_t *)img_info->mem_base;
		return buf[(addr - img_info->base_addr)/2];
	}
	else {
		struct io_device * dev = get_device(addr);
		if (dev != NULL){
			return dev->read_data(dev, addr, 2);
		}
		else {
			printf("%s->error at %x\n",__func__,addr);
			return 0;
		}
	}
}

void store_half_word(uint32_t addr, unsigned short data)
{
	if (get_addr_type(addr) == MEM_ADDR){
		uint16_t * buf = (uint16_t *)img_info->mem_base;
		buf[(addr - img_info->base_addr) / 2] = data;
	}
	else {
		struct io_device * dev = get_device(addr);
		if (dev != NULL){
			dev->write_data(dev, addr, data, 2);
		}
		else {
			printf("%s->unhandled data:%x at %x\n",__func__,data,addr);
		}
	}
}

uint32_t get_word(uint32_t addr)
{
	if (get_addr_type(addr) == MEM_ADDR){
		uint32_t * buf = (uint32_t * )img_info->mem_base;
		return buf[(addr - img_info->base_addr) / 4];
	}
	else {
		struct io_device * dev = get_device(addr);
		if (dev != NULL){
			return dev->read_data(dev, addr, 4);
		}
		else {
			printf("%s->error at %x\n",__func__,addr);
			return 0;
		}
	}
}

void store_word(uint32_t addr, uint32_t data)
{
	if (get_addr_type(addr) == MEM_ADDR){
		uint32_t * buf = (uint32_t *)img_info->mem_base;
		buf[(addr - img_info->base_addr) / 4] = data;
	}
	else {
		struct io_device * dev = get_device(addr);
		if (dev != NULL){
			dev->write_data(dev, addr, data, 4);
		}
		else {
			printf("%s->unhandled data:%x at %x\n",__func__,data,addr);
		}
	}
}

static uint32_t has_irq(struct NIOS_CPU * cpu)
{
	uint32_t ret_val = CPU_HAS_NO_EVENT;
	uint32_t irq_mask = 0;
	uint32_t temp = 0;
	
	/* 1. judge the device has irq */
	irq_mask = get_io_irq_status();

	if (irq_mask != 0){
		if ((cpu->ctrl_regs[status] & REG_STATUS_PIE) == REG_STATUS_PIE){
			temp = cpu->ctrl_regs[ienable] & irq_mask;
#if 1
			if (temp != 0 && (temp != cpu->ctrl_regs[ipending])){
#else
			if (temp != 0){
#endif
				/* 2. modify the control register */
				cpu->ctrl_regs[ipending] |= temp;
				ret_val = CPU_HAS_IRQ;
			}
		}
	}
	
	return ret_val;
}

static uint32_t has_exception(struct NIOS_CPU * cpu)
{
	uint32_t ret_val = CPU_HAS_NO_EVENT;
	
	return ret_val;
}

static uint32_t get_cpu_status(struct NIOS_CPU * cpu)
{
	uint32_t ret_val = CPU_HAS_NO_EVENT;
	ret_val |= has_irq(cpu);
	ret_val |= has_exception(cpu);
	
	return ret_val;
}

static void handle_irq(struct NIOS_CPU * cpu)
{
	uint32_t reg_status_val = 0;
	uint32_t temp_pc = 0;
	/* according to the kind of interrupt or exception,
	 * set PC to the address of IRQ or exception vector.
	 */
	temp_pc = cpu->pc;
	cpu->pc = EXCEPTION_HANDLER_ADDR;
	reg_status_val = cpu->ctrl_regs[status];
	cpu->ctrl_regs[status] &= 0xFFFFFFFE; /* clear PIE */
	if((reg_status_val & REG_STATUS_EH) == 0){
		cpu->ctrl_regs[estatus] = reg_status_val;
		cpu->gp_regs[ea] = temp_pc + 4;
	}
}

static void handle_exception(struct NIOS_CPU * cpu)
{

}

void handle_irq_exception(struct NIOS_CPU * cpu)
{
	uint32_t cpu_status = CPU_HAS_NO_EVENT;
	
	cpu_status = get_cpu_status(cpu);
	if(cpu_status & CPU_HAS_IRQ){
		handle_irq(cpu);
	}
	else if (cpu_status & CPU_HAS_EXCEPTION){
		handle_exception(cpu);
	}
}

static void dump_j_code(uint32_t code)
{
	def_j_type_code;
	
	printf("TYPE: J Code [%08X]\n", code);
	printf("OP: 0x%02X\n",instr->op);
	printf("IMM26: 0x%X\n",instr->imm26);
}

static void dump_r_code(uint32_t code)
{
	def_r_type_code;
	
	printf("TYPE: R Code [%08X]\n", code);
	printf("OP: 0x%02X\n", instr->op);
	printf("A=0x%X B=0x%X C=0x%X OPX=0x%X N=0x%X\n",
			instr->a, instr->b, instr->c, instr->opx, instr->n);
}

static void dump_i_code(uint32_t code)
{
	def_i_type_code;
	
	printf("TYPE: I Code [%08X]\n", code);
	printf("OP: 0x%02X\n", instr->op);
	printf("A=0x%X B=0x%X IMM16=0x%04X\n", instr->a, instr->b, instr->imm16);
}

static void dump_code(uint32_t code)
{
	uint32_t code_type = get_opcode(code);
	printf("\n");
	printf("--------------Code Dump--------------\n");
	switch(code_type){
	case OP_J_TYPE:
		dump_j_code(code);
		break;
	case OP_R_TYPE:
		dump_r_code(code);
		break;
	default:
		dump_i_code(code);
		break;
	}
	printf("-------------------------------------\n");
}

void dump_curr_code(uint32_t code)
{
	dump_code(code);
}

void dump_next_code(struct NIOS_CPU * cpu)
{
	uint32_t code = get_instruct(cpu,img_info->mem_base, img_info->base_addr);
	dump_code(code);
}

void dump_pc(struct NIOS_CPU * cpu)
{
	int32_t i = 0;
	printf("============================================================\n");
	printf("ERROR PC=%08X\n",cpu->pc);
	printf("PC Trace Index :%d\n", cpu->trace_index);
	printf("============================================================\n");
	for (i=0;i<PC_TRACE_CNT;i++){
		if (i == (cpu->trace_index - 1)){
			printf("(%08X) ",cpu->pc_trace[i]);
		}
		else {
			printf("%08X ",cpu->pc_trace[i]);
		}
		if (((i+1)% 8) == 0){
			printf("\n");
		}
	}
	printf("\n============================================================\n");
}

static const char * g_regs_name[] = {
	" ZERO",
	"   AT",
	"REG02",
	"REG03",
	"REG04",
	"REG05",
	"REG06",
	"REG07",
	"REG08",
	"REG09",
	"REG10",
	"REG11",
	"REG12",
	"REG13",
	"REG14",
	"REG15",
	"REG16",
	"REG17",
	"REG18",
	"REG19",
	"REG20",
	"REG21",
	"REG22",
	"REG23",
	"   ET",
	"   BT",
	"   GP",
	"   SP",
	"   FP",
	"   EA",
	"   BA",
	"   RA"
}; 

static const char * c_regs_name[] = {
	"SSTAT",
	"ESTAT",
	"BSTAT",
	"IENAB",
	"IPEND",
	"CPUID",
	"RESER",
	"EXCEP",
	"PTEAD",
	"TLBAD",
	"TLBMI",
	"RESER",
	"BADAD",
	"CONFI",
	"MPUBA",
	"MPUAD"
};

void dump_register(struct NIOS_CPU * cpu)
{
	int32_t i = 0;
	
	printf("\n");
	printf("============================================================\n");
	printf("[Genernal Register]\n");
	printf("============================================================\n");
	for (i=0;i<NIOS_REG_CNT;i++){
		printf("%s=%08X ",g_regs_name[i],cpu->gp_regs[i]);
		if (((i+1) % 4) == 0)
			printf("\n");
	}
      	printf("============================================================\n");
	printf("[Control Register]\n");
	printf("============================================================\n");
	for (i=0;i<16;i++){
		printf("%s=%08X ",c_regs_name[i],cpu->ctrl_regs[i]);
		if (((i+1) % 4) == 0)
			printf("\n");
	}
	printf("============================================================\n");
}

/**
 * Parse the options of break command
 */
#define SPACE_CHAR (' ')
#define BREAK_CMD_LEN (16)
extern char break_func_name[256];
static uint32_t get_break_pc(char * input)
{
	uint32_t ret_pc = 0x0;
	uint32_t input_len = 0;

	input_len = strlen(input);	
	/* 
	 * TODO: the judgement of option's format is not full implemented.
	 */
	if (input_len == BREAK_CMD_LEN){
		sscanf(input, "break 0x%08x\n", &ret_pc);
	}
	
	if (strstr(input, "func") != NULL){
		memset(break_func_name, 0x00, 256);
		sscanf(input, "break func=%s", break_func_name);
		return 0xFFFFFFFF;
	}
	return ret_pc;
}

void set_break(struct NIOS_CPU * cpu, char * input)
{
	uint32_t break_pc = 0x0;

	break_pc = get_break_pc(input);
	if (break_pc != 0x0){
		if (break_pc == 0xFFFFFFFF){
			cpu->mode = BREAK_MODE;
		}
		else {
			cpu->mode = BREAK_MODE;
			cpu->break_pc = break_pc;
		}
	}
	else{
		printf("Error Address\n");
		cpu->mode = SINGLE_STEP;
	}
}

static void output_char(char c)
{
	if (c == '\r' ||  c == '\n'){
		printf(".");
	}
	else {
		printf("%c",c);
	}
}
static void output_ascii(uint32_t * buf)
{
	uint32_t val = 0;
	char temp = 0;
	uint32_t i = 0;

	printf("\t");
	for (i = 0; i < 4; i++){
		val = buf[i];
		temp = val & 0xFF;
		output_char(temp);
		temp = (val >> 8) & 0xFF;
		output_char(temp);
		temp = (val >> 16) & 0xFF;
		output_char(temp);
		temp = (val >> 24) & 0xFF;
		output_char(temp);
	}
}
/*
 * dump the content of memory
 */
void dump_mem(struct NIOS_CPU * cpu, char * input)
{
	uint32_t addr, size;
	struct image_info * info = get_image_info();
	int32_t i = 0;
	uint32_t * buf = NULL;
	uint32_t temp_buf[4] = {0};
	uint32_t index = 0;
	sscanf(input, "dump 0x%X %d", &addr, &size);
	
	addr = addr & (~0x3);
	size = size / 4 * 4;
	if (addr >= info->base_addr && size < info->mem_size){
		buf = info->mem_base + (addr - info->base_addr) / 4 ;
		printf("================================================\n");
		printf("[Memory Dump start:0x%08X size:%d]\n", addr, size);
		printf("================================================\n");
		for (i=0;i<size/4;i++){
			temp_buf[index] = buf[i];
			index = index + 1;
			if (i % 4 == 0){
				printf("[%08X]",addr + i * 4);
			}
			printf("%08X ", (uint32_t)buf[i]);

			if (((i+1) % 4) == 0){
				output_ascii(temp_buf);
				index = 0;
				printf("\n");
			}
			else if (i == (size/4 - 1)){
				printf("\n");
			}
		}
		printf("================================================\n");
	}
}

void clean_ipending(uint32_t mask)
{
	cpu.ctrl_regs[ipending] &= (~mask);
}

/* Record the pc trace. It is used when segment fault happening. */
void trace_pc(struct NIOS_CPU * cpu)
{
	cpu->pc_trace[cpu->trace_index] = cpu->pc;
	cpu->trace_index ++;
	if (cpu->trace_index >= PC_TRACE_CNT){
		cpu->trace_index = 0;
	}
}
/*----------------------------------------------------------------------------*/

